﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SimaticLib;
using System.Collections.ObjectModel;
using NLog;
using S7HCOM_XLib;
using System.Collections;
using DMCBase;
using System.Threading;
using System.IO;
using System.Diagnostics;
using Ionic.Zip;
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks;
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Projectfolders.Step7V5;
using DotNetSiemensPLCToolBoxLibrary.DataTypes.Projectfolders;
using DotNetSiemensPLCToolBoxLibrary.DataTypes;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using DotNetSiemensPLCToolBoxLibrary.General;


namespace S7_GenerateSource
{
    class S7 : NotifyPropertyChangedBase
    {
        [DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hwnd, int nCmdShow);
        [DllImport("user32.dll")]
        public static extern int SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
        [DllImport("user32.dll")]
        public static extern bool IsWindow(IntPtr hWnd);

        #region Private Variables
        private String _LeftProjectPath;
        private String _RightProjectPath;

        private Simatic _Simatic;

        private IS7Project _LeftProject;
        private IS7Project _RightProject;

        static Logger Log = LogManager.GetCurrentClassLogger();
        
        private String _LeftSourceFilePath = Path.GetTempPath() + "DMC_Merge" + "\\sL_";
        private String _RightSourceFilePath = Path.GetTempPath() + "DMC_Merge" + "\\sR_";
        private String _MergedSourceFilePath = Path.GetTempPath() + "DMC_Merge" + "\\sM_";

        private String _ExtractLeftProjectPath = "";
        private String _ExtractRightProjectPath = "";

        private Dictionary<String, Block> _LeftBlocks;
        private Dictionary<String, Block> _RightBlocks;
        private Dictionary<String, Blocks> _AllBlocks;

        private Boolean _IsBusy = false;
        private Int16 _ProgressBarCurrent;
        private Int16 _ProgressBarMax;

        private SimaticCache _dicLeftSimaticCache = new SimaticCache();
        private SimaticCache _dicRightSimaticCache = new SimaticCache();

        private String _StatusBarText = "Ready";

        #endregion

        #region Public Properties

        private ObservableCollection<LogEvent> _LogModel;
        public ObservableCollection<LogEvent> LogModel
        {
            get
            {
                return _LogModel;
            }

            set
            {
                _LogModel = value;
            }
        }

        public KeyValuePair<String, Blocks> CurrentBlock { get; set; }

        public String LeftProjectPath
        {
            get
            {
                if (_LeftProjectPath == null)
                {
                    LeftProjectPath = Properties.Settings.Default.LeftProjectPath;
                }
                return _LeftProjectPath;
            }
            set
            {               
                _LeftProjectPath = value;
                NotifyPropertyChanged("LeftProjectPath");
            }
        }

        public String RightProjectPath
        {
            get
            {
                if (_RightProjectPath == null)
                {
                    RightProjectPath = Properties.Settings.Default.RightProjectPath;
                }
                return _RightProjectPath;
            }
            set
            {
                _RightProjectPath = value;               
                NotifyPropertyChanged("RightProjectPath");
            }
        }

        public String ExtractLeftProjectPath
        {
            get
            {
                return _ExtractLeftProjectPath;
            }
            set
            {
                _ExtractLeftProjectPath = value;
                NotifyPropertyChanged("ExtractLeftProjectPath");
            }
        }

        public String ExtractRightProjectPath
        {
            get
            {
                return _ExtractRightProjectPath;
            }
            set
            {
                _ExtractRightProjectPath = value;
                NotifyPropertyChanged("ExtractRightProjectPath");
            }
        }

        public Dictionary<String, Block> LeftBlocks
        {
            get
            {
                if (_LeftBlocks == null)
                {
                    _LeftBlocks = new Dictionary<String, Block>();
                }
                
                return _LeftBlocks;
                
            }
            set
            {
                _LeftBlocks = value;
            }
        }

        public Dictionary<String, Block> RightBlocks
        {
            get
            {
                if (_RightBlocks == null)
                {
                    _RightBlocks = new Dictionary<String, Block>();
                }
                return _RightBlocks;
            }
            set
            {
                _RightBlocks = value;
            }
        }

        public Dictionary<String, Blocks> AllBlocks
        {
            get
            {
                if (_AllBlocks == null)
                {
                    _AllBlocks = new Dictionary<String, Blocks>();
                }
                return _AllBlocks;
            }
            set
            {
                _AllBlocks = value;
            }
        }

        public Boolean IsBusy
        {
            get
            {
                return _IsBusy;
            }
            set
            {
                _IsBusy = value;
                NotifyPropertyChanged("IsBusy");
            }
        }

        public Int16 ProgressBarCurrent
        {
            get
            {
                return _ProgressBarCurrent;
            }
            set
            {
                _ProgressBarCurrent = value;
                NotifyPropertyChanged("ProgressBarCurrent");
            }
        }
        public Int16 ProgressBarMax
        {
            get
            {
                return _ProgressBarMax;
            }
            set
            {
                _ProgressBarMax = value;
                NotifyPropertyChanged("ProgressBarMax");
            }
        }

        public String StatusBarText
        {
            get
            {
                return _StatusBarText;
            }

            set
            {
                _StatusBarText = value;
                NotifyPropertyChanged("StatusBarText");
            }
        }

        #endregion

        #region Model Initialization and Unloading
        
        public S7()
        {
            ClearTempDirectories();
            SimaticInit();
        }

        private void SimaticInit()
        {
            //_Simatic = new Simatic();
        }

        internal void ClearTempDirectories()
        {
            try
            {
                if (_Simatic != null)
                {
                    _Simatic.Close();
                    _Simatic = null;
                }

                DeleteFolder(ExtractLeftProjectPath);
                DeleteFolder(ExtractRightProjectPath);
                DeleteFolder(Path.GetTempPath() + "DMC_Merge");
            }
            catch (Exception ex)
            {
                Debug.WriteLine("S7.ClearTempDirectories. Exception: " + ex.ToString());
            }
        }
        #endregion


        #region Public Command Functions

        internal void GetBlocks()
        {
            DoJob(new ThreadStart(() =>
            {
                // User forced to re-load projects 
                ExtractRightProjectPath = "";
                ExtractLeftProjectPath = "";
                GetBlocksAsync(_RightProjectPath, _LeftProjectPath);
            }));
        }


        internal void Save()
        {
            DoJob(new ThreadStart(() =>
            {
                if (_ExtractRightProjectPath != "")
                {
                    FireInfo("Archiving.");
                    saveZippedProject(_ExtractRightProjectPath, _RightProjectPath);
                    FireInfo("Archive complete.");
                }
            }));
        }


        internal void CopyBlockToRight()
        {
            DoJob(new ThreadStart(CopyBlockToRightAsync));
        }
        
        internal void StartDiffProcess()
        {
            DoJob(new ThreadStart(StartDiffProcessAsync));
        }

        #endregion


        #region Private Command Implementations
        
        internal void GetBlocksAsync(string strRightProjectPath, string strLeftProjectPath)
        {
            FireInfo("Loading blocks.");

            // Trying to find a project name from the path
            strRightProjectPath = GetProjectFileNameFromPath(strRightProjectPath);
            strLeftProjectPath = GetProjectFileNameFromPath(strLeftProjectPath);
            DotNetSiemensPLCToolBoxLibrary.Projectfiles.Project left = DotNetSiemensPLCToolBoxLibrary.Projectfiles.Projects.LoadProject(strLeftProjectPath, false);
            DotNetSiemensPLCToolBoxLibrary.Projectfiles.Project right = DotNetSiemensPLCToolBoxLibrary.Projectfiles.Projects.LoadProject(strRightProjectPath, false);
            
            //grab blocks from projects
            List<DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.ProjectBlockInfo> blkPojectLeft = GetBlocksFromProject(left.ProjectStructure);
            List<DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.ProjectBlockInfo> blkPojectRight = GetBlocksFromProject(right.ProjectStructure);

            LeftBlocks.Clear();
            RightBlocks.Clear();

            FireInfo("Parsing left project.");
            foreach (ProjectBlockInfo item in blkPojectLeft)
            {
                if (item != null)
                {
                    DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block blk = (DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block)item.GetBlock();
                    
                    LeftBlocks.Add(item.ParentFolder.ToString().Replace("(zipped)", "") + "\\" + blk.BlockName, CreateNewBlock(item));
                }
            }

            FireInfo("Parsing right project.");
            foreach (ProjectBlockInfo item in blkPojectRight)
            {
                //if ((item != null) && (item.BlockType != PLCBlockType.SDB) && (item.BlockType != PLCBlockType.SFB) && (item.BlockType != PLCBlockType.SFC))
                if (item != null)
                {
                    DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block blk = (DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block)item.GetBlock();
                    RightBlocks.Add(item.ParentFolder.ToString().Replace("(zipped)", "") + "\\" + blk.BlockName, CreateNewBlock(item));
                }
            }

            foreach (String key in LeftBlocks.Keys)
            {
                if (!RightBlocks.Keys.Contains(key))
                {
                    RightBlocks.Add(key, new Block() { Name = LeftBlocks[key].Name, SymbolicName = "-------NOT FOUND-------" });
                    LeftBlocks[key].NameSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].SizeSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].SymbolicNameSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].ModifiedSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].NameSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].SizeSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].SymbolicNameSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].ModifiedSimilarity = BlockSimilarityType.Orphan;
                }
            }

            foreach (String key in RightBlocks.Keys)
            {
                if (!LeftBlocks.Keys.Contains(key))
                {
                    LeftBlocks.Add(key, new Block() { Name = RightBlocks[key].Name, SymbolicName = "-------NOT FOUND-------" });
                    LeftBlocks[key].NameSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].SizeSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].SymbolicNameSimilarity = BlockSimilarityType.Orphan;
                    LeftBlocks[key].ModifiedSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].NameSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].SizeSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].SymbolicNameSimilarity = BlockSimilarityType.Orphan;
                    RightBlocks[key].ModifiedSimilarity = BlockSimilarityType.Orphan;
                }
            }

            foreach (String key in RightBlocks.Keys)
            {
                if (RightBlocks[key].SizeSimilarity != BlockSimilarityType.Orphan)
                {
                    RightBlocks[key].NameSimilarity = RightBlocks[key].Name.Equals(LeftBlocks[key].Name) ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    RightBlocks[key].SizeSimilarity = RightBlocks[key].Size == LeftBlocks[key].Size ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    RightBlocks[key].SymbolicNameSimilarity = RightBlocks[key].SymbolicName.Equals(LeftBlocks[key].SymbolicName) ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    RightBlocks[key].ModifiedSimilarity = RightBlocks[key].Modified == LeftBlocks[key].Modified ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    LeftBlocks[key].NameSimilarity = RightBlocks[key].Name.Equals(LeftBlocks[key].Name) ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    LeftBlocks[key].SizeSimilarity = RightBlocks[key].Size == LeftBlocks[key].Size ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    LeftBlocks[key].SymbolicNameSimilarity = RightBlocks[key].SymbolicName.Equals(LeftBlocks[key].SymbolicName) ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                    LeftBlocks[key].ModifiedSimilarity = RightBlocks[key].Modified == LeftBlocks[key].Modified ? BlockSimilarityType.Identical : BlockSimilarityType.Different;
                }
            }

            Dictionary<String, Blocks> newAllBlocks = new Dictionary<string, Blocks>();
            foreach (String key in RightBlocks.Keys)
            {
                newAllBlocks.Add(key, new Blocks() { LeftBlock = LeftBlocks[key], RightBlock = RightBlocks[key] });
            }
            AllBlocks = newAllBlocks;

            // Assuming we got a s7 v5 project - close them
            ((DotNetSiemensPLCToolBoxLibrary.Projectfiles.Step7ProjectV5)left).Dispose();
            ((DotNetSiemensPLCToolBoxLibrary.Projectfiles.Step7ProjectV5)right).Dispose();

            NotifyPropertyChanged("LeftBlocks");
            NotifyPropertyChanged("RightBlocks");
            NotifyPropertyChanged("AllBlocks");

            FireInfo("Process completed.");
        }


        private Block CreateNewBlock(ProjectBlockInfo item)
        {
            DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block blk = (DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7Block)item.GetBlock();
            if (blk.BlockLanguage == PLCLanguage.SRC)
            {
                // Special logic for source block
                DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7SourceBlock srcBlk = (DotNetSiemensPLCToolBoxLibrary.DataTypes.Blocks.Step7V5.S7SourceBlock)blk;
                return new BlockSource()
                    {
                        Modified = srcBlk.LastCodeChange,
                        Name = srcBlk.BlockName,
                        Language = srcBlk.BlockLanguage.ToString(),
                        Size = srcBlk.CodeSize,
                        SymbolicName = Regex.Match(item.ToString(), @"(?<=\().+?(?=\))").Value,
                        SourceText = srcBlk.Text,
                        Filename = srcBlk.Filename,
                        AllowToMerge = true,
                        AllowToCopy = true
                    };
            }
            else
            {
                // Not a source block.
                return new Block()
                    {
                        Modified = blk.LastCodeChange,
                        Name = blk.BlockName,
                        Language = blk.BlockLanguage.ToString(),
                        Size = blk.CodeSize,
                        SymbolicName = Regex.Match(item.ToString(), @"(?<=\().+?(?=\))").Value,
                        AllowToMerge = blk.BlockLanguage != PLCLanguage.SCL &&
                                       blk.BlockLanguage != PLCLanguage.F_CALL &&
                                       blk.BlockLanguage != PLCLanguage.GRAPH && 
                                       (
                                           blk.BlockType == PLCBlockType.DB ||
                                           blk.BlockType == PLCBlockType.FB ||
                                           blk.BlockType == PLCBlockType.FC ||
                                           blk.BlockType == PLCBlockType.OB ||
                                           blk.BlockType == PLCBlockType.UDT ||
                                           blk.BlockType == PLCBlockType.VAT 
                                        ),
                        AllowToCopy = blk.BlockLanguage != PLCLanguage.SCL &&
                                      blk.BlockLanguage != PLCLanguage.F_CALL
                    };
            }
        }

        private String GetProjectFileNameFromPath(String strPath)
        {
            if (strPath.ToLower().EndsWith(".s7p"))
            {
                return strPath;
            }
            else if (strPath.ToLower().EndsWith(".zip"))
            {
                return strPath;
            }
            else
            {
                // This is probably folder name
                if (Directory.Exists(strPath))
                {
                    var Projects = Directory.EnumerateFiles(strPath, "*.s7p", SearchOption.AllDirectories);
                    if (Projects.Count() > 0)
                    {
                        return Projects.First();
                    }
                }
            }

            return strPath;
        }

        private List<ProjectBlockInfo> GetBlocksFromProject(ProjectFolder project)
        {
            //create new list
            List<ProjectBlockInfo> allBlocks = new List<ProjectBlockInfo>();

            if (project.GetType() == typeof(BlocksOfflineFolder))
            {
                BlocksOfflineFolder offline = (BlocksOfflineFolder)project;
                allBlocks.AddRange(offline.BlockInfos);
            }

            if (project.GetType() == typeof(SourceFolder))
            {
                SourceFolder source = (SourceFolder)project;
                // TODO: Use source files instead of the compiled FBs?
                allBlocks.AddRange(source.BlockInfos);
            }

            //add all child blocks
            foreach (ProjectFolder subFolder in project.SubItems)
            {
                allBlocks.AddRange(GetBlocksFromProject(subFolder));
            }
            
            return allBlocks;
        }


        internal void StartDiffProcessAsync()
        {
            IsBusy = true;

            //_Simatic = new Simatic();

            ProgressBarMax = 10;            

            if (CurrentBlock.Value.LeftBlock.ModifiedSimilarity == BlockSimilarityType.Orphan)
            {
                FireError("Can't merge orphan blocks.");
                return;
            }

            if (_Simatic == null)
            {
                FireInfo("Creating Simatic instance.");
                _Simatic = new Simatic();
            }
            ProgressBarCurrent++;

            // Attaching both projects
            LoadingBothSimaticProjects();

            // Special logic to merge SCL files
            if (CurrentBlock.Value.LeftBlock.Language == "SRC" && CurrentBlock.Value.RightBlock.Language == "SRC")
            {
                S7Source rightBlock;
                S7Source leftBlock;

                FireInfo("Searching for the right block in Simatic structure.");
                rightBlock = FindCurrentSourceInProgram(_RightProject.Programs, ref _dicRightSimaticCache);
                ProgressBarCurrent++;

                FireInfo("Searching for the left block in Simatic structure.");
                leftBlock = FindCurrentSourceInProgram(_LeftProject.Programs, ref _dicLeftSimaticCache);
                ProgressBarCurrent++;

                // Should have both blocks
                if (leftBlock == null || rightBlock == null)
                {
                    FireError("Can't find block in the Simatic structure.");
                    return;
                }

                if (!Directory.Exists(_LeftSourceFilePath)) Directory.CreateDirectory(_LeftSourceFilePath);
                String leftSourceFileName = _LeftSourceFilePath + leftBlock.Name + ".scl";
                leftBlock.Export(leftSourceFileName);
                ProgressBarCurrent++;

                if (!Directory.Exists(_RightSourceFilePath)) Directory.CreateDirectory(_RightSourceFilePath);
                String rightSourceFileName = _RightSourceFilePath + rightBlock.Name + ".scl";
                rightBlock.Export(rightSourceFileName);
                ProgressBarCurrent++;

                String mergedSourceFileName = _MergedSourceFilePath + rightBlock.Name + ".scl";

                Process DiffProcess = new Process
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = "TortoiseMerge.exe",
                        Arguments = "/base:\"" + leftSourceFileName + "\" /mine:\"" + rightSourceFileName + "\" /merged:\"" + mergedSourceFileName + "\""
                    }
                };
                DiffProcess.Start();
                DiffProcess.WaitForExit();
                ProgressBarCurrent++;

                if (File.Exists(mergedSourceFileName))
                {                    
                    // Can't find any gracefull way of importing source block - just override the file\
                    String strSrcFile = ExtractRightProjectPath + ((BlockSource)CurrentBlock.Value.RightBlock).Filename.Replace(@"/", @"\");
                    File.Copy(mergedSourceFileName, strSrcFile, true);

                    FireInfo("Compiling block");
                    S7SWItems s7swReturn = rightBlock.Compile();
                    //IntPtr hwnd = (IntPtr)rightBlock.AppWindowHandle;
                    //ShowWindow(hwnd, 6);    // 0 = hide the window, 6 = minimize

                    if (s7swReturn.Count <= 0)
                    {
                        // Something is not right - show edit window
                        FireError("Can't compile block, please resolve compilation errors");
                        //ShowWindow(hwnd, 3);    // 3 - maximize
                        rightBlock.Edit();

                        // Wait till they fixed it and close the window
                        //while (IsWindow(hwnd)) ;
                    }
                    else
                    {
                        // Close editor window
                        FireInfo("Compiled successfully");
                        //SendMessage(hwnd, 0x0112, 0xF060, 0); // 0xF060 = SC_CLOSE; 0x0112 = WM_SYSCOMMAND                                
                    }             
                }
            }
            else
            {
                // Standard FB/FC/DB

                S7Block leftBlock = null;
                S7Block rightBlock = null;

                FireInfo("Searching for the right block in Simatic structure.");
                rightBlock = FindCurrentBlockInProgram(_RightProject.Programs, ref _dicRightSimaticCache);
                ProgressBarCurrent++;

                FireInfo("Searching for the left block in Simatic structure.");
                leftBlock = FindCurrentBlockInProgram(_LeftProject.Programs, ref _dicLeftSimaticCache);
                ProgressBarCurrent++;                
                
                // Should have both blocks
                if (leftBlock == null || rightBlock == null)
                {
                    FireError("Can't find block in the Simatic structure.");
                    return;
                }

                FireInfo("Generating right source.");
                if (!Directory.Exists(_RightSourceFilePath)) Directory.CreateDirectory(_RightSourceFilePath);
                String rightSourceFileName = _RightSourceFilePath + rightBlock.Name + ".awl";
                rightBlock.GenerateSource(rightSourceFileName, S7GenerateSourceFlags.S7GSFDoOverwrite);
                ProgressBarCurrent++;

                FireInfo("Generating left source.");
                if (!Directory.Exists(_LeftSourceFilePath)) Directory.CreateDirectory(_LeftSourceFilePath);
                String leftSourceFileName = _LeftSourceFilePath + leftBlock.Name + ".awl";
                leftBlock.GenerateSource(leftSourceFileName, S7GenerateSourceFlags.S7GSFDoOverwrite);
                ProgressBarCurrent++;

                String mergedSourceFileName = _MergedSourceFilePath + rightBlock.Name + ".awl";

                Process DiffProcess = new Process
                {
                    StartInfo = new ProcessStartInfo
                    {
                        FileName = "TortoiseMerge.exe",
                        Arguments = "/base:\"" + leftSourceFileName + "\" /mine:\"" + rightSourceFileName + "\" /merged:\"" + mergedSourceFileName + "\""
                    }
                };
                DiffProcess.Start();
                DiffProcess.WaitForExit();
                ProgressBarCurrent++;

                if (File.Exists(mergedSourceFileName))
                {
                    S7SWItems s7swItems = rightBlock.Program.Next;
                    for (int i = 1; i <= s7swItems.Count; i++)
                    {
                        if (((S7Container)s7swItems[i]).ConcreteType == S7ContainerType.S7SourceContainer)
                        {
                            IS7Source3 s7Source = (IS7Source3)s7swItems[i].Next.Add("Tmprc", S7SWObjType.S7Source, mergedSourceFileName);

                            FireInfo("Compiling block.");
                            S7SWItems s7swReturn = s7Source.Compile();
                            IntPtr hwnd = (IntPtr)s7Source.AppWindowHandle;
                            ShowWindow(hwnd, 6);    // 0 = hide the window, 6 = minimize

                            if (s7swReturn.Count <= 0)
                            {
                                // Something is not right - show edit window
                                FireError("Can't compile block, please resolve compilation errors.");
                                ShowWindow(hwnd, 3);    // 3 - maximize
                                s7Source.Edit();

                                // Wait till they fixed it and close the window
                                while (IsWindow(hwnd)) ;
                            }
                            else
                            {
                                // Close editor window
                                FireInfo("Compiled successfully.");
                                //SendMessage(hwnd, 0x0112, 0xF060, 0); // 0xF060 = SC_CLOSE; 0x0112 = WM_SYSCOMMAND                                
                            }

                            // Remove the source                            
                            s7Source.Remove();

                            // S7 editor doesn't like close message. Minimize it
                            ShowWindow(hwnd, 6);

                            break;
                        }
                    }
                }
            }
            ProgressBarCurrent++;

            // TODO: Remove temp files

            // Refresh list
            FireInfo("Refreshing projects.");
            GetBlocksAsync(ExtractRightProjectPath, ExtractLeftProjectPath);

            ProgressBarCurrent = ProgressBarMax;
            
            FireInfo("Process completed.");
            IsBusy = false;
        }


        internal void CopyBlockToRightAsync()
        {            
            if (CurrentBlock.Key.Count() <= 0)
            {
                return;
            }

            IsBusy = true;
            ProgressBarMax = 10;
            ProgressBarCurrent = 1;

            S7Block leftBlock = null, rightBlock = null;
            if (_Simatic == null)
            {
                FireInfo("Creating Simatic instance.");
                _Simatic = new Simatic();
            }

            // Trying to find loaded projects
            LoadingBothSimaticProjects();

            FireInfo("Searching for the left block in Simatic structure.");
            leftBlock = FindCurrentBlockInProgram(_LeftProject.Programs, ref _dicLeftSimaticCache);
            ProgressBarCurrent++;

            FireInfo("Searching for the right block in Simatic structure.");
            rightBlock = FindCurrentBlockInProgram(_RightProject.Programs, ref _dicRightSimaticCache);
            ProgressBarCurrent++;

            FireInfo("Copying block.");
            if (leftBlock != null)
            {
                // Source block exists
                if (rightBlock != null)
                {
                    // Target block also exists - copy!
                    rightBlock.Remove();
                    leftBlock.Copy(rightBlock.Parent);
                }
                else
                {
                    FireInfo("Searching for the right parent folder in the Simatic structure.");
                    IS7BlockContainer rightFolder = FindBlockFolderInProgram(CurrentBlock, _RightProject.Programs, ref _dicRightSimaticCache);
                    if (rightFolder != null)
                    {
                        // Right block does not exists - create a new one
                        leftBlock.Copy(rightFolder);

                        // Just created a new block - need to refersh right cache
                        this._dicRightSimaticCache.dicProgramsLogPath.Clear();
                    }
                    else
                    {
                        FireError("Can't find corresponding foder in the right Simatic project.");
                    }
                }                
            }
            else
            {
                // Source block does not exists - remove target block
                rightBlock.Remove();

                // Just deleted a block - need to refersh cache
                this._dicRightSimaticCache.dicProgramsLogPath.Clear();
            }
            ProgressBarCurrent++;

            // Spare step
            ProgressBarCurrent++;

            // Spare step
            ProgressBarCurrent++;

            FireInfo("Refreshing projects.");
            GetBlocksAsync(ExtractRightProjectPath, ExtractLeftProjectPath);

            ProgressBarCurrent = ProgressBarMax;

            FireInfo("Process completed.");
            IsBusy = false;
        }

        #endregion

        
        #region Helper Functions


        private bool DeleteFolder(string strPath)
        {
            try
            {
                if (Directory.Exists(strPath)) Directory.Delete(strPath, true);
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        private Dictionary<String, Block> GetBlocksFromProject(IS7Project Project)
        {
            Dictionary<String, Block> newBlocks = new Dictionary<String, Block>();
            for (int i = 1; i <= Project.Programs.Count; i++)
            {
                for (int j = 1; j <= Project.Programs[i].Next.Count; j++)
                {
                    if (((S7Container)Project.Programs[i].Next[j]).ConcreteType == S7ContainerType.S7BlockContainer)
                    {
                        IS7BlockContainer BlocksFolder = (IS7BlockContainer)Project.Programs[i].Next[j];
                        Console.WriteLine("IS7BlockContainer: " + BlocksFolder.Name + ": " + BlocksFolder.LogPath);
                        foreach (IS7Block block in BlocksFolder.Next)
                        {
                            
                            if ((block.ConcreteType != S7BlockType.S7SDBs) && (block.ConcreteType != S7BlockType.S7VAT) && (block.ConcreteType != S7BlockType.S7SFB) && (block.ConcreteType != S7BlockType.S7SFC) && !block.Language.Equals("SCL"))
                            {
                                ProgressBarCurrent++;
                                ProgressBarMax = (short)(BlocksFolder.Next.Count * 2);
                                Block newBlock = new Block()
                                {
                                    Name = block.Name,
                                    //Size = block.Size,
                                    SymbolicName = block.SymbolicName,
                                    Modified = block.Modified
                                };
                                
                                if (block.ConcreteType != S7BlockType.S7UDT)
                                {
                                    newBlock.Size = block.Size;
                                }
                                newBlocks.Add(block.LogPath, newBlock);
                                if (!newBlocks.Keys.Contains(block.Program.SymbolTable.LogPath))
                                {
                                    newBlocks.Add(block.Program.SymbolTable.LogPath, new Block()
                                    {
                                        Name = "---",
                                        SymbolicName = block.Program.SymbolTable.Name,
                                        Modified = block.Program.SymbolTable.Modified
                                    });
                                }
                                        

                            }
                        }
                    }
                }
            }
            return newBlocks;
        }



        private S7Block FindCurrentBlockInProgram(S7Programs Programs, ref SimaticCache SimaticCache)
        {
            return FindBlockInProgram(CurrentBlock, Programs, ref SimaticCache);
        }


        private S7Block FindBlockInProgram(KeyValuePair<String, Blocks> BlockToFind, S7Programs Programs, ref SimaticCache SimaticCache)
        {
            List<String> PathParts = (BlockToFind.Key.Split('\\')).ToList<String>();
            String BlocksFolderPath = BlockToFind.Key.Remove(BlockToFind.Key.LastIndexOf('\\'));
            String ProgramFolderPath = BlocksFolderPath.Remove(BlocksFolderPath.LastIndexOf('\\'));

            SimaticCacheUpdate(Programs, ref SimaticCache);

            var prgs = from ds in SimaticCache.dicProgramsLogPath where ds.Value.strProgramLogPath == ProgramFolderPath select ds;
            if (prgs.Count() > 0)
            {
                var prg = prgs.First();
                var blks = from ds in prg.Value.dicBlocksLogPath where ds.Value == BlocksFolderPath select ds;
                if (blks.Count() > 0)
                {
                    var blk = blks.First();
                    try
                    {
                        return (S7Block)Programs[prg.Key].Next[blk.Key].Next[PathParts.Last()];
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("S7.FindBlockToFindInProgram. Exception: " + ex.ToString());
                        return null;
                    }
                }
            }

            // Can't find it
            return null;
        }


        private IS7BlockContainer FindBlockFolderInProgram(KeyValuePair<String, Blocks> FolderToFind, S7Programs Programs, ref SimaticCache SimaticCache)
        {
            String BlocksFolderPath = FolderToFind.Key.Remove(FolderToFind.Key.LastIndexOf('\\'));
            String ProgramFolderPath = BlocksFolderPath.Remove(BlocksFolderPath.LastIndexOf('\\'));

            SimaticCacheUpdate(Programs, ref SimaticCache);

            var prgs = from ds in SimaticCache.dicProgramsLogPath where ds.Value.strProgramLogPath == ProgramFolderPath select ds;
            if (prgs.Count() > 0)
            {
                var prg = prgs.First();                
                var blks = from ds in prg.Value.dicBlocksLogPath where ds.Value == BlocksFolderPath select ds;
                if (blks.Count() > 0)
                {
                    var blk = blks.First();
                    try
                    {
                        return (IS7BlockContainer)Programs[prg.Key].Next[blk.Key];
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("S7.FindBlockFolderInProgram. Exception: " + ex.ToString());
                        return null;
                    }
                }
            }

            // Can't find it
            return null;
        }


        private S7Source FindCurrentSourceInProgram(S7Programs Programs, ref SimaticCache SimaticCache)
        {
            return FindSourceInProgram(CurrentBlock, Programs, ref SimaticCache);
        }


        private S7Source FindSourceInProgram(KeyValuePair<String, Blocks> BlockToFind, S7Programs Programs, ref SimaticCache SimaticCache)
        {
            List<String> PathParts = (BlockToFind.Key.Split('\\')).ToList<String>();
            String BlocksFolderPath = BlockToFind.Key.Remove(BlockToFind.Key.LastIndexOf('\\'));
            String ProgramFolderPath = BlocksFolderPath.Remove(BlocksFolderPath.LastIndexOf('\\'));

            SimaticCacheUpdate(Programs, ref SimaticCache);

            var prgs = from ds in SimaticCache.dicProgramsLogPath where ds.Value.strProgramLogPath == ProgramFolderPath select ds;
            if (prgs.Count() > 0)
            {
                var prg = prgs.First();
                var blks = from ds in prg.Value.dicBlocksLogPath where ds.Value == BlocksFolderPath select ds;
                if (blks.Count() > 0)
                {
                    var blk = blks.First();
                    try
                    {
                        return (S7Source)Programs[prg.Key].Next[blk.Key].Next[PathParts.Last()];
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("S7.FindSourceInProgram. Exception: " + ex.ToString());
                        return null;
                    }
                }
            }

            // Can't find it
            return null;
        }

        
        private void SimaticCacheUpdate(S7Programs Programs, ref SimaticCache SimaticCache)
        {
            // if dictionary is not populated
            if (SimaticCache.dicProgramsLogPath.Count == 0)
            {
                for (int i = 1; i <= Programs.Count; i++)
                {                    
                    SimaticCache.dicProgramsLogPath.Add(i, new SimaticCache.SimaticBlocksCache());

                    SimaticCache.dicProgramsLogPath[i].strProgramLogPath = Programs[i].LogPath;

                    for (int j = 1; j <= Programs[i].Next.Count; j++)
                    {
                        SimaticCache.dicProgramsLogPath[i].dicBlocksLogPath.Add(j, Programs[i].Next[j].LogPath);
                    }                    
                }
            }
        }


        private void LoadingBothSimaticProjects ()
        {
                    // Trying to find loaded projects
            FireInfo("Loading left project to Simatic.");
            if (ExtractLeftProjectPath != "")
            {
                _LeftProject = getSimaticProject(GetProjectFileNameFromPath(ExtractLeftProjectPath));
            }
            else
            {                
                if (IsZippedProject(LeftProjectPath))
                {
                    // Zipped project - Unzip it first
                    ExtractLeftProjectPath = Path.GetTempPath() + "DMC_Merge" + "\\pLeft\\";
                    _LeftProject = getSimaticProject(UnzipProject(LeftProjectPath, ExtractLeftProjectPath));
                }
                else
                {
                    // Unzipped project - attach
                    ExtractLeftProjectPath = LeftProjectPath;
                    _LeftProject = getSimaticProject(GetProjectFileNameFromPath(LeftProjectPath));
                }
            }
            ProgressBarCurrent++;

            FireInfo("Loading right project to Simatic.");
            if (ExtractRightProjectPath != "")
            {
                _RightProject = getSimaticProject(GetProjectFileNameFromPath(ExtractRightProjectPath));
            }
            else
            {
                if (IsZippedProject(RightProjectPath))
                {
                    // Zipped project - Unzip it first
                    ExtractRightProjectPath = Path.GetTempPath() + "DMC_Merge" + "\\pRight\\";
                    _RightProject = getSimaticProject(UnzipProject(RightProjectPath, ExtractRightProjectPath));
                }
                else
                {
                    // Unzipped project - attach
                    ExtractRightProjectPath = RightProjectPath;
                    _RightProject = getSimaticProject(GetProjectFileNameFromPath(RightProjectPath));
                }
            }
            ProgressBarCurrent++;
        }


        private IS7Project getSimaticProject(string ProjectPath)
        {
            if (ProjectPath.ToLower().EndsWith(".s7p"))
            {
                return _Simatic.Projects.Add(ProjectPath);
            }
            else
            {
                var Projects = Directory.EnumerateFiles(ProjectPath, "*.s7p", SearchOption.AllDirectories);
                if (Projects.Count() > 0)
                {
                    return _Simatic.Projects.Add(Projects.First());
                }
                else
                {
                    throw new FileNotFoundException("s7p file not found in the folder!");
                }
            }
        }


        private bool IsZippedProject(string ProjectPath)
        {
            return ProjectPath.ToLower().EndsWith(".zip");
        }


        private string UnzipProject(string ProjectPath, string ExtractedProjectPath)
        {
            if (ProjectPath.ToLower().EndsWith(".zip"))
            {
                // Clear extracted folder
                DeleteFolder(ExtractedProjectPath);

                using (ZipFile zip = ZipFile.Read(ProjectPath))
                {                    
                    zip.ExtractAll(ExtractedProjectPath, ExtractExistingFileAction.OverwriteSilently);
                    var Projects = Directory.EnumerateFiles(ExtractedProjectPath, "*.s7p", SearchOption.AllDirectories);
                    if (Projects.Count() > 0)
                    {
                        // Return path to the s7p file
                        return Projects.First();
                    }
                }
            }

            return "";
        }


        private void saveZippedProject(string ExtractedProjectPath, string ProjectPath)
        {
            if (ProjectPath.ToLower().EndsWith(".s7p"))
            {
                // Unpacked project - do nothing
            }
            else if (!ProjectPath.ToLower().EndsWith(".zip"))
                throw new ArgumentException("Path must be .s7p or .zip!", "Path");
            else 
            {
                try
                {
                    if (_Simatic != null)
                    {
                        _Simatic.Close();
                        _Simatic = null;
                    }

                    // Archive with SharpZipLib, it seems to be faster
                    File.Delete(ProjectPath);
                    ZipHelper zip = new ZipHelper("");
                    zip.ArchiveFolder(ExtractedProjectPath, ProjectPath);

                    ExtractRightProjectPath = "";
                    ExtractLeftProjectPath = "";
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("S7.saveZippedProject. Exception: " + ex.ToString());
                }
            }
        }

        

        #endregion

        #region Log Events
        public event EventHandler<EventArgs<LogEvent>> LogEvent;
        private void FireTrace(string message, params object[] args)
        {
            OnLogEventOccurred(string.Format(message, args), S7_GenerateSource.LogEvent.Severity.Trace);
        }

        private void FireInfo(string message, params object[] args)
        {
            OnLogEventOccurred(string.Format(message, args), S7_GenerateSource.LogEvent.Severity.Info);
        }

        private void FireWarning(string message, params object[] args)
        {
            OnLogEventOccurred(string.Format(message, args), S7_GenerateSource.LogEvent.Severity.Warning);
        }

        private void FireError(string message, params object[] args)
        {
            OnLogEventOccurred(string.Format(message, args), S7_GenerateSource.LogEvent.Severity.Error);
        }

        private void OnLogEventOccurred(string message, LogEvent.Severity severity)
        {
            EventHandler<EventArgs<LogEvent>> handler = LogEvent;

            if (handler != null)
            {
                handler(
                    this,
                    new EventArgs<LogEvent>(
                        new LogEvent()
                        {
                            LogSeverity = severity,
                            Message = message,
                            Timestamp = DateTime.Now,
                        }));
            }
        }

        #endregion

        #region Multi-threaded Helpers
        private Thread jobThread;
        private Thread workerThread;

        private void DoJob(ThreadStart workerThreadStart)
        {
            jobThread = new Thread(JobThread);
       //     jobThread.SetApartmentState(ApartmentState.STA);
            jobThread.Start(workerThreadStart);
        }

        private void JobThread(object workerThreadStart)
        {
            if (!(workerThreadStart is ThreadStart))
            {
                throw new ArgumentException("Parameter must be of type ThreadStart", "workerThreadStart");
            }

            ProgressBarCurrent = 0;
            ProgressBarMax = 1;

            IsBusy = true;

            workerThread = new Thread(workerThreadStart as ThreadStart);
            //workerThread.SetApartmentState(ApartmentState.STA);
            workerThread.Start();
            
  
            // Wait for worker thread to finish
            workerThread.Join();
            IsBusy = false;
           
        }

        #endregion


    }
}
 